package com.springboot.learn.retry;

import com.springboot.learn.retry.message.RetryMessage;
import com.springboot.learn.retry.model.MyRetryContext;
import com.springboot.learn.retry.model.RetryTargetInfo;
import com.springboot.learn.retry.repository.RetryTaskRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Controller;

import javax.annotation.PostConstruct;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.TimeUnit;

/**
 * 重试任务执行器
 *
 * @author BG343674
 * created by BG343674 on 2019/11/23
 */
@Slf4j
@Controller
public class RetryTaskActuator implements Observer {

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private RetryMessage retryMessage;
    @Autowired
    private RetryTaskRepository retryTaskRepository;

    @PostConstruct
    public void init() {
        retryMessage.addObserver(this);
    }

    /**
     * 执行重试任务
     *
     * @param obj
     */
    @Override
    public void update(Observable o, Object obj) {
        MyRetryContext retryContext = (MyRetryContext) obj;
        RetryTargetInfo retryTargetInfo = retryContext.getRetryTargetInfo();
        retryContext.setTime(System.currentTimeMillis());
        getMethodInvokeThreadExecutor().execute(() -> {
            try {
                log.info("任务[{} {}#{}]开始第{}次重试", retryContext.getUuid(), retryTargetInfo.getClass().getName(), retryTargetInfo.getMethod(), retryContext.getRetryCount() + 1);
                retryTargetInfo.invoke();
                retryContext.setException(null);
                log.info("任务[{}]重试成功", retryContext.getUuid());
            } catch (Exception e) {
                log.error(ExceptionUtils.getRootCauseMessage(e), e);
                retryContext.setException(e);
            } finally {
                preHandle(retryContext);
                retryTaskRepository.updateStatus(retryContext);
            }
        });
    }

    public void preHandle(MyRetryContext retryContext) {
        retryContext.setRetryCount(retryContext.getRetryCount() + 1);
        boolean haveException = retryContext.getException() != null;
        if (!haveException) {
            retryContext.setStatus(4);
            return;
        }
        Long nextRetryTime = getNextRetryTime(retryContext);
        boolean noTimes = nextRetryTime == null;
        if (noTimes) {
            retryContext.setStatus(3);
            return;
        }
        retryContext.setTime(nextRetryTime);
    }

    /**
     * 等待规则，获取第几次重试等待的时间
     *
     * @param retryContext 重试信息
     * @return 等待的时间（毫秒）
     */
    private static Long getNextRetryTime(MyRetryContext retryContext) {

        // 重试5次 秒数
        Integer[] wait = {1, 2, 3, 4, 5};
        int length = wait.length;
        int count = retryContext.getRetryCount();
        if (count + 1 > length) {
            return null;
        }
        return System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(wait[count], TimeUnit.SECONDS);
    }

    /**
     * 获取执行重试任务的线程组
     *
     * @return
     */
    public ThreadPoolTaskExecutor getMethodInvokeThreadExecutor() {
        return threadPoolTaskExecutor;
    }

}
